
#ifndef _TTG_PIPELINE_NODEWRAPPER_
#define _TTG_PIPELINE_NODEWRAPPER_
#include "Common/Definitions.h"

#include "Node.h"
#include "ContextedNode.h"
#include <vector>

namespace ttg
{
	namespace pipeline
	{
		//Wrappes Node. Can be useful for node attachment.
		//operator + means serial connection.
		//operator * means parallel connection.
		//Note: destructor will NOT release associated nodes.
		template <class InType, class OutType>
		class NodeWrapper
		{
			private:
				std::vector<ttg_internal::pipeline::ParametrizedInput<OutType> *> outputs;
				std::vector<ttg_internal::pipeline::ParametrizedOutput<InType> *> inputs;
				NodeWrapper(std::vector<ttg_internal::pipeline::ParametrizedOutput<InType> *> &inputs,
							std::vector<ttg_internal::pipeline::ParametrizedInput<OutType> *> &outputs)
				{
					this->outputs = outputs;
					this->inputs = inputs;
				}
			public:
				//Creates wrapper for given node.
				NodeWrapper(Node<InType, OutType> *node)
				{
					outputs.push_back(node);
					inputs.push_back(node);
				}
				//Creates wrapper for given node.
				NodeWrapper(Node<InType, OutType> &node)
				{
					outputs.push_back(&node);
					inputs.push_back(&node);
				}
				//Creates wrapper for given node.
				NodeWrapper(ContextedNode<InType, OutType> *node)
				{
					outputs.push_back(node);
					inputs.push_back(node);
				}
				//Creates wrapper for given node.
				NodeWrapper(ContextedNode<InType, OutType> &node)
				{
					outputs.push_back(&node);
					inputs.push_back(&node);
				}
				//Returns input tails (essences, that receives task data).
				inline std::vector<ttg_internal::pipeline::ParametrizedOutput<InType> *> &getInputs()
						{ return inputs; }
				//Returns output tails (essences, that produces task data).
				inline std::vector<ttg_internal::pipeline::ParametrizedInput<OutType> *> &getOutputs()
						{ return outputs; }
				//Serial connection: attaches all inputs to single output, or vice versa.
				//Note: in case of multiple inputs and multiple outputs it throws Error.
				template<class InType, class MidType, class OutType>
				friend NodeWrapper<InType, OutType> operator +(NodeWrapper<InType, MidType> &w1,
															   NodeWrapper<MidType, OutType> &w2);
				//Parallel connection: unites all outputs and unites all inputs.
				//Result will be viewable as one node with multiple otputs and multiple inputs.
				template<class InType, class OutType>
				friend NodeWrapper<InType, OutType> operator *(NodeWrapper<InType, OutType> &w1,
															   NodeWrapper<InType, OutType> &w2);
		};

		template<class InType, class OutType>
		NodeWrapper<InType, OutType> operator *(NodeWrapper<InType, OutType> &w1,
											    NodeWrapper<InType, OutType> &w2)
		{
			NodeWrapper<InType, OutType> res(w1.inputs, w1.outputs);
			res.inputs.insert(res.inputs.begin(), w2.inputs.begin(), w2.inputs.end());
			res.outputs.insert(res.outputs.begin(), w2.outputs.begin(), w2.outputs.end());
			return res;
		}

		template<class InType, class MidType, class OutType>
		NodeWrapper<InType, OutType> operator +(NodeWrapper<InType, MidType> &w1,
												NodeWrapper<MidType, OutType> &w2)
		{
			if (w1.outputs.size() == 1)
			{
				for (size_t i = 0; i < w2.inputs.size(); i++)
				{
					w1.getOutputs()[0]->attachOutput(w2.getInputs()[i]);
					w2.getInputs()[i]->attachInput(w1.getOutputs()[0]);
				}
				return NodeWrapper<InType, OutType>(w1.inputs, w2.outputs);
			} else
			if (w2.inputs.size() == 1)
			{
				for (size_t i = 0; i < w1.outputs.size(); i++)
				{
					w2.inputs[0]->attachInput(w1.outputs[i]);
					w1.outputs[i]->attachOutput(w2.inputs[0]);
				}
				return NodeWrapper<InType, OutType>(w1.inputs, w2.outputs);
			}
			else
				throw Error(Error::FATAL,
							"NodeWrapper",
							"operator +",
							"Multiple inputs + multiple outputs are not allowed");
		}

		//Wraps node. Can be used for auto select of template args.
		template <class InType, class OutType>
		NodeWrapper<InType, OutType> wrap(Node<InType, OutType> *node)
		{
			return NodeWrapper<InType, OutType>(node);
		}
		//Wraps node. Can be used for auto select of template args.
		template <class InType, class OutType>
		NodeWrapper<InType, OutType> wrap(Node<InType, OutType> &node)
		{
			return NodeWrapper<InType, OutType>(node);
		}
		//Wraps node. Can be used for auto select of template args.
		template <class InType, class OutType>
		NodeWrapper<InType, OutType> wrap(ContextedNode<InType, OutType> *node)
		{
			return NodeWrapper<InType, OutType>(node);
		}
		//Wraps node. Can be used for auto select of template args.
		template <class InType, class OutType>
		NodeWrapper<InType, OutType> wrap(ContextedNode<InType, OutType> &node)
		{
			return NodeWrapper<InType, OutType>(node);
		}
	}
}

#endif
